# 协同过滤--User-Based CF评分预测
import numpy as np
import pandas as pd


def predictByUser(uid, iid, ratings_matrix, similarity):
    '''
    预测给定用户对给定物品的评分值
    :param uid: 用户id
    :param iid: 物品id
    :param ratings_matrix:用户-物品评分矩阵
    :param similarity: 用户两两相似度矩阵
    :return: 预测的评分数
    '''
    # 1.找出uid用户的相似用户 移除自身（drop([uid])），并移除没有评分记录的用户（dropna()）
    similar_users = similarity[uid].drop([uid]).dropna()

    # 相似用户筛选规则：正相关的用户 （即相似度大于0的用户）
    similar_users = similar_users.where(similar_users > 0).dropna()
    if similar_users.empty is True:
        print("用户<%d>没有相似的用户" % uid)
        return None

    # 2.从uid用户的近邻相似用户中筛选出对iid物品有评分记录的近邻用户
    ids = set(ratings_matrix[iid].dropna().index) & set(similar_users.index)
    final_similar_users = similar_users.loc[list(ids)]

    # 3.结合uid用户与其近邻用户的相似度预测uid用户对iid物品的评分
    sum_up = 0  # 保存评分预测公式的分子
    sum_down = 0  # 用来保存分母
    for sim_uid, similarity in final_similar_users.iteritems():
        # 近邻用户的评分数据
        sim_user_rated_movies = ratings_matrix.loc[sim_uid].dropna()
        # 近邻用户对iid物品的评分
        sim_user_rating_for_item = sim_user_rated_movies[iid]
        # 计算分子的值
        sum_up += similarity * sim_user_rating_for_item
        # 计算分母的值
        sum_down += similarity
    # 计算预测的评分值并返回
    if sum_down == 0:
        print("没有足够的数据来预测用户<%d>对物品<%d>的评分" % (uid, iid))
        return None
    predict_rating = sum_up / sum_down
    return round(predict_rating)

# 根据物品相似度来进行评分预测预测，实现物品推荐
def recommendItemsByUser(user_id, ratings_matrix, user_similar, k=5):
    '''
    推荐给定用户的物品
    :param uid: 用户id
    :param ratings_matrix: 用户-物品评分矩阵
    :param similarity: 用户两两相似度矩阵
    :param k: 推荐物品的数量
    :return: 推荐物品的列表（数组形式）
    '''
    user_ratings = ratings_matrix.loc[user_id].dropna()  # 获取用户评分过的物品
    recommendations = []
    for item_id in user_similar.index:
        # 检查用户是否已经评分过该物品
        if item_id not in user_ratings.index:
            # 如果用户未对物品评分，则进行预测
            prediction = predictByUser(user_id,item_id,ratings_matrix,user_similar)
            if prediction is not None:
                recommendations.append((item_id, prediction))

    # 按预测评分降序排序推荐物品
    if recommendations:  # 如果有推荐
        recommendations.sort(key=lambda x: x[1], reverse=True)
        return [item[0] for item in recommendations[:k]]  # 返回评分最高的k个物品的ID
    else:
        return []  # 如果没有推荐，则返回空列表

if __name__ == '__main__':
    # 读取数据
    dtype = {"userid": np.int32, "movieId": np.int32, "rating": np.float32}
    ratings = pd.read_csv(r"D:\Project\FootShopping\Product-Recommended\data\ratings_data.csv", dtype=dtype,
                          usecols=range(3))


    ratings_matrix = ratings.pivot_table(index=["userId"], columns=["movieId"], values="rating")
    similarity = ratings_matrix.T.corr()

    # 在调用 predictByUser 之前，确保 uid 是字符串
    uid = 12
    k = 5  # 推荐5个物品
    recommended_item_ids = recommendItemsByUser(uid, ratings_matrix, similarity, k)
    print(recommended_item_ids)